// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Testing;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks
{
    public class HeaderCollectionBenchmark
    {
        private const int InnerLoopCount = 1024 * 1024;

        private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
        private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager();
        private HttpResponseHeaders _responseHeadersDirect;
        private HttpResponse _response;

        public enum BenchmarkTypes
        {
            ContentLengthNumeric,
            ContentLengthString,
            Plaintext,
            Common,
            Unknown
        }

        [Params(
            BenchmarkTypes.ContentLengthNumeric,
            BenchmarkTypes.ContentLengthString,
            BenchmarkTypes.Plaintext,
            BenchmarkTypes.Common,
            BenchmarkTypes.Unknown
        )]
        public BenchmarkTypes Type { get; set; }

        [Benchmark(OperationsPerInvoke = InnerLoopCount)]
        public void SetHeaders()
        {
            switch (Type)
            {
                case BenchmarkTypes.ContentLengthNumeric:
                    SetContentLengthNumeric(InnerLoopCount);
                    break;
                case BenchmarkTypes.ContentLengthString:
                    SetContentLengthString(InnerLoopCount);
                    break;
                case BenchmarkTypes.Plaintext:
                    SetPlaintext(InnerLoopCount);
                    break;
                case BenchmarkTypes.Common:
                    SetCommon(InnerLoopCount);
                    break;
                case BenchmarkTypes.Unknown:
                    SetUnknown(InnerLoopCount);
                    break;
            }
        }

        [Benchmark(OperationsPerInvoke = InnerLoopCount)]
        public void GetHeaders()
        {
            switch (Type)
            {
                case BenchmarkTypes.ContentLengthNumeric:
                    GetContentLengthNumeric(InnerLoopCount);
                    break;
                case BenchmarkTypes.ContentLengthString:
                    GetContentLengthString(InnerLoopCount);
                    break;
                case BenchmarkTypes.Plaintext:
                    GetPlaintext(InnerLoopCount);
                    break;
                case BenchmarkTypes.Common:
                    GetCommon(InnerLoopCount);
                    break;
                case BenchmarkTypes.Unknown:
                    GetUnknown(InnerLoopCount);
                    break;
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void SetContentLengthNumeric(int count)
        {
            for (var i = 0; i < count; i++)
            {
                _responseHeadersDirect.Reset();

                _response.ContentLength = 0;
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void SetContentLengthString(int count)
        {
            for (var i = 0; i < count; i++)
            {
                _responseHeadersDirect.Reset();

                _response.Headers[HeaderNames.ContentLength] = "0";
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void SetPlaintext(int count)
        {
            for (var i = 0; i < count; i++)
            {
                AddHeaders();
            }

            void AddHeaders()
            {
                _responseHeadersDirect.Reset();

                _response.StatusCode = 200;
                _response.ContentType = "text/plain";
                _response.ContentLength = 13;

                var dateHeaderValues = _dateHeaderValueManager.GetDateHeaderValues();
                _responseHeadersDirect.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
                _responseHeadersDirect.SetRawServer("Kestrel", _bytesServer);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void SetCommon(int count)
        {
            for (var i = 0; i < count; i++)
            {
                AddHeaders();
            }

            void AddHeaders()
            {
                _responseHeadersDirect.Reset();

                _response.StatusCode = 200;
                _response.ContentType = "text/css";
                _response.ContentLength = 421;

                var headers = _response.Headers;

                headers.Connection = "Close";
                headers.CacheControl = "public, max-age=30672000";
                headers.Vary = "Accept-Encoding";
                headers.ContentEncoding = "gzip";
                headers.Expires = "Fri, 12 Jan 2018 22:01:55 GMT";
                headers.LastModified = "Wed, 22 Jun 2016 20:08:29 GMT";
                headers.SetCookie = "prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric";
                headers.ETag = "\"54ef7954-1078\"";
                headers.TransferEncoding = "chunked";
                headers.ContentLanguage = "en-gb";
                headers.Upgrade = "websocket";
                headers.Via = "1.1 varnish";
                headers.AccessControlAllowOrigin = "*";
                headers.AccessControlAllowCredentials = "true";
                headers.AccessControlExposeHeaders = "Client-Protocol, Content-Length, Content-Type, X-Bandwidth-Est, X-Bandwidth-Est2, X-Bandwidth-Est-Comp, X-Bandwidth-Avg, X-Walltime-Ms, X-Sequence-Num";

                var dateHeaderValues = _dateHeaderValueManager.GetDateHeaderValues();
                _responseHeadersDirect.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
                _responseHeadersDirect.SetRawServer("Kestrel", _bytesServer);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void SetUnknown(int count)
        {
            for (var i = 0; i < count; i++)
            {
                AddHeaders();
            }

            void AddHeaders()
            {
                _responseHeadersDirect.Reset();

                _response.StatusCode = 200;
                _response.ContentType = "text/plain";
                _response.ContentLength = 13;

                var headers = _response.Headers;

                headers.Link = "<https://www.gravatar.com/avatar/6ae816bfaad7bbc58b17fac49ef5cced?d=404&s=250>; rel=\"canonical\"";
                headers.XUACompatible = "IE=Edge";
                headers.XPoweredBy = "ASP.NET";
                headers.XContentTypeOptions = "nosniff";
                headers.XXSSProtection = "1; mode=block";
                headers.XFrameOptions = "SAMEORIGIN";
                headers.StrictTransportSecurity = "max-age=31536000; includeSubDomains; preload";
                headers.ContentSecurityPolicy = "default-src 'none'; script-src 'self' cdnjs.cloudflare.com code.jquery.com scotthelme.disqus.com a.disquscdn.com www.google-analytics.com go.disqus.com platform.twitter.com cdn.syndication.twimg.com; style-src 'self' a.disquscdn.com fonts.googleapis.com cdnjs.cloudflare.com platform.twitter.com; img-src 'self' data: www.gravatar.com www.google-analytics.com links.services.disqus.com referrer.disqus.com a.disquscdn.com cdn.syndication.twimg.com syndication.twitter.com pbs.twimg.com platform.twitter.com abs.twimg.com; child-src fusiontables.googleusercontent.com fusiontables.google.com www.google.com disqus.com www.youtube.com syndication.twitter.com platform.twitter.com; frame-src fusiontables.googleusercontent.com fusiontables.google.com www.google.com disqus.com www.youtube.com syndication.twitter.com platform.twitter.com; connect-src 'self' links.services.disqus.com; font-src 'self' cdnjs.cloudflare.com fonts.gstatic.com fonts.googleapis.com; form-action 'self'; upgrade-insecure-requests;";

                var dateHeaderValues = _dateHeaderValueManager.GetDateHeaderValues();
                _responseHeadersDirect.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
                _responseHeadersDirect.SetRawServer("Kestrel", _bytesServer);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private long? GetContentLengthNumeric(int count)
        {
            long? length = null;
            for (var i = 0; i < count; i++)
            {
                length = _response.ContentLength;
            }

            return length;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private string GetContentLengthString(int count)
        {
            string value = null;
            for (var i = 0; i < count; i++)
            {
                value = _response.Headers[HeaderNames.ContentLength];
            }

            return value;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private string GetPlaintext(int count)
        {
            string value = null;
            for (var i = 0; i < count; i++)
            {
                value = ReadHeaders();
            }

            return value;

            string ReadHeaders()
            {
                var headers = _response.Headers;
                var value = headers.Date ;
                value = headers.Server ;
                value = headers.ContentType ;
                return value;
            }

        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private string GetCommon(int count)
        {
            string value = null;
            for (var i = 0; i < count; i++)
            {
                value = ReadHeaders();
            }

            return value;

            string ReadHeaders()
            {
                var headers = _response.Headers;
                var value = headers.Date ;
                value = headers.Server ;
                value = headers.ContentType ;

                value = headers.Connection ;
                value = headers.CacheControl ;
                value = headers.Vary ;
                value = headers.ContentEncoding ;
                value = headers.Expires ;
                value = headers.LastModified ;
                value = headers.SetCookie ;
                value = headers.ETag ;
                value = headers.TransferEncoding ;
                value = headers.ContentLanguage ;
                value = headers.Upgrade ;
                value = headers.Via ;
                value = headers.AccessControlAllowOrigin ;
                value = headers.AccessControlAllowCredentials ;
                value = headers.AccessControlExposeHeaders ;

                return value;
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private string GetUnknown(int count)
        {
            string value = null;
            for (var i = 0; i < count; i++)
            {
                value = ReadHeaders();
            }

            return value;

            string ReadHeaders()
            {
                var headers = _response.Headers;
                value = headers.Date ;
                value = headers.Server ;
                value = headers.ContentType ;


                value = headers.Link ;
                value = headers.XUACompatible ;
                value = headers.XPoweredBy ;
                value = headers.XContentTypeOptions ;
                value = headers.XXSSProtection ;
                value = headers.XFrameOptions ;
                value = headers.StrictTransportSecurity ;
                value = headers.ContentSecurityPolicy ;

                return value;
            }
        }

        [IterationSetup]
        public void Setup()
        {
            var memoryPool = PinnedBlockMemoryPoolFactory.Create();
            var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
            var pair = DuplexPipe.CreateConnectionPair(options, options);

            var serviceContext = TestContextFactory.CreateServiceContext(
                serverOptions: new KestrelServerOptions(),
                httpParser: new HttpParser<Http1ParsingHandler>(),
                dateHeaderValueManager: _dateHeaderValueManager,
                log: new MockTrace());

            var connectionContext = TestContextFactory.CreateHttpConnectionContext(
                serviceContext: serviceContext,
                connectionContext: null,
                transport: pair.Transport,
                memoryPool: memoryPool,
                connectionFeatures: new FeatureCollection());

            var http1Connection = new Http1Connection(connectionContext);

            http1Connection.Reset();
            serviceContext.DateHeaderValueManager.OnHeartbeat(DateTimeOffset.UtcNow);

            _responseHeadersDirect = (HttpResponseHeaders)http1Connection.ResponseHeaders;
            var context = new DefaultHttpContext(http1Connection);
            _response = context.Response;

            switch (Type)
            {
                case BenchmarkTypes.ContentLengthNumeric:
                    SetContentLengthNumeric(1);
                    GetContentLengthNumeric(1);
                    break;
                case BenchmarkTypes.ContentLengthString:
                    SetContentLengthString(1);
                    GetContentLengthString(1);
                    break;
                case BenchmarkTypes.Plaintext:
                    SetPlaintext(1);
                    GetPlaintext(1);
                    break;
                case BenchmarkTypes.Common:
                    SetCommon(1);
                    GetCommon(1);
                    break;
                case BenchmarkTypes.Unknown:
                    SetUnknown(1);
                    GetUnknown(1);
                    break;
            }
        }
    }
}
